From 35a367e02e435d9919fd671b59b9da09c1cc95f4 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 29 Aug 2023 02:51:41 +0300 Subject: [PATCH] [ENH]: Auth Providers - Static API Token (#1051) Refs: #1027 ## Description of changes *Summarize the changes made by this PR.* - New functionality - Baseline functionality and tests implemented - Example notebook updated - Minor refactor on the client creds provider to allow for user specific credentials fetching. ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python (regression) - [x] New fixtures added for token-based auth ## Documentation Changes Docs should be updated to highlight the new supported auth method. --- chromadb/auth/providers.py | 2 +- chromadb/auth/token/__init__.py | 193 ++++++++++++++++++ chromadb/config.py | 2 + chromadb/test/auth/test_token_auth.py | 134 ++++++++++++ chromadb/test/conftest.py | 58 ++++-- docker-compose.yml | 1 + .../basic_functionality/client_auth.ipynb | 149 +++++++++++++- 7 files changed, 517 insertions(+), 22 deletions(-) create mode 100644 chromadb/test/auth/test_token_auth.py diff --git a/chromadb/auth/providers.py b/chromadb/auth/providers.py index a3bb236..3123c52 100644 --- a/chromadb/auth/providers.py +++ b/chromadb/auth/providers.py @@ -1,6 +1,6 @@ import importlib import logging -from typing import cast, Dict, TypeVar, Any +from typing import cast, Dict, TypeVar, Any, Optional import requests from overrides import override diff --git a/chromadb/auth/token/__init__.py b/chromadb/auth/token/__init__.py index e69de29..ec6dcdc 100644 --- a/chromadb/auth/token/__init__.py +++ b/chromadb/auth/token/__init__.py @@ -0,0 +1,193 @@ +import logging +import string +from enum import Enum +from typing import Tuple, Any, cast, Dict, TypeVar + +from overrides import override +from pydantic import SecretStr + +from chromadb.auth import ( + ServerAuthProvider, + ClientAuthProvider, + ServerAuthenticationRequest, + ServerAuthCredentialsProvider, + AuthInfoType, + ClientAuthCredentialsProvider, + ClientAuthResponse, + SecretStrAbstractCredentials, + AbstractCredentials, +) +from chromadb.auth.registry import register_provider, resolve_provider +from chromadb.config import System +from chromadb.utils import get_class + +T = TypeVar("T") + +logger = logging.getLogger(__name__) + +__all__ = ["TokenAuthServerProvider", "TokenAuthClientProvider"] + +_token_transport_headers = ["Authorization", "X-Chroma-Token"] + + +class TokenTransportHeader(Enum): + AUTHORIZATION = "Authorization" + X_CHROMA_TOKEN = "X-Chroma-Token" + + +class TokenAuthClientAuthResponse(ClientAuthResponse): + _token_transport_header: TokenTransportHeader + + def __init__( + self, + credentials: SecretStr, + token_transport_header: TokenTransportHeader = TokenTransportHeader.AUTHORIZATION, + ) -> None: + self._credentials = credentials + self._token_transport_header = token_transport_header + + @override + def get_auth_info_type(self) -> AuthInfoType: + return AuthInfoType.HEADER + + @override + def get_auth_info(self) -> Tuple[str, SecretStr]: + if self._token_transport_header == TokenTransportHeader.AUTHORIZATION: + return "Authorization", SecretStr( + f"Bearer {self._credentials.get_secret_value()}" + ) + elif self._token_transport_header == TokenTransportHeader.X_CHROMA_TOKEN: + return "X-Chroma-Token", SecretStr( + f"{self._credentials.get_secret_value()}" + ) + else: + raise ValueError( + f"Invalid token transport header: {self._token_transport_header}" + ) + + +def check_token(token: str) -> None: + token_str = str(token) + if not all(c in string.ascii_letters + string.digits for c in token_str): + raise ValueError("Invalid token. Must contain only ASCII letters and digits.") + + +@register_provider("token_config") +class TokenConfigServerAuthCredentialsProvider(ServerAuthCredentialsProvider): + _token: SecretStr + + def __init__(self, system: System) -> None: + super().__init__(system) + system.settings.require("chroma_server_auth_credentials") + token_str = str(system.settings.chroma_server_auth_credentials) + check_token(token_str) + self._token = SecretStr(token_str) + + @override + def validate_credentials(self, credentials: AbstractCredentials[T]) -> bool: + _creds = cast(Dict[str, SecretStr], credentials.get_credentials()) + if "token" not in _creds: + logger.error("Returned credentials do not contain token") + return False + return _creds["token"].get_secret_value() == self._token.get_secret_value() + + +class TokenAuthCredentials(SecretStrAbstractCredentials): + _token: SecretStr + + def __init__(self, token: SecretStr) -> None: + self._token = token + + @override + def get_credentials(self) -> Dict[str, SecretStr]: + return {"token": self._token} + + @staticmethod + def from_header( + header: str, + token_transport_header: TokenTransportHeader = TokenTransportHeader.AUTHORIZATION, + ) -> "TokenAuthCredentials": + """ + Extracts token from header and returns a TokenAuthCredentials object. + """ + if token_transport_header == TokenTransportHeader.AUTHORIZATION: + header = header.replace("Bearer ", "") + header = header.strip() + token = header + elif token_transport_header == TokenTransportHeader.X_CHROMA_TOKEN: + header = header.strip() + token = header + else: + raise ValueError( + f"Invalid token transport header: {token_transport_header}" + ) + return TokenAuthCredentials(SecretStr(token)) + + +@register_provider("token") +class TokenAuthServerProvider(ServerAuthProvider): + _credentials_provider: ServerAuthCredentialsProvider + _token_transport_header: TokenTransportHeader = TokenTransportHeader.AUTHORIZATION + + def __init__(self, system: System) -> None: + super().__init__(system) + self._settings = system.settings + system.settings.require("chroma_server_auth_credentials_provider") + self._credentials_provider = cast( + ServerAuthCredentialsProvider, + system.require( + resolve_provider( + str(system.settings.chroma_server_auth_credentials_provider), + ServerAuthCredentialsProvider, + ) + ), + ) + if system.settings.chroma_server_auth_token_transport_header: + self._token_transport_header = TokenTransportHeader[ + str(system.settings.chroma_server_auth_token_transport_header) + ] + + @override + def authenticate(self, request: ServerAuthenticationRequest[Any]) -> bool: + try: + _auth_header = request.get_auth_info( + AuthInfoType.HEADER, self._token_transport_header.value + ) + return self._credentials_provider.validate_credentials( + TokenAuthCredentials.from_header( + _auth_header, self._token_transport_header + ) + ) + except Exception as e: + logger.error(f"TokenAuthServerProvider.authenticate failed: {repr(e)}") + return False + + +@register_provider("token") +class TokenAuthClientProvider(ClientAuthProvider): + _credentials_provider: ClientAuthCredentialsProvider[Any] + _token_transport_header: TokenTransportHeader = TokenTransportHeader.AUTHORIZATION + + def __init__(self, system: System) -> None: + super().__init__(system) + self._settings = system.settings + + system.settings.require("chroma_client_auth_credentials_provider") + self._credentials_provider = system.require( + get_class( + str(system.settings.chroma_client_auth_credentials_provider), + ClientAuthCredentialsProvider, + ) + ) + _token = self._credentials_provider.get_credentials() + check_token(_token.get_secret_value()) + if system.settings.chroma_client_auth_token_transport_header: + self._token_transport_header = TokenTransportHeader[ + str(system.settings.chroma_client_auth_token_transport_header) + ] + + @override + def authenticate(self) -> ClientAuthResponse: + _token = self._credentials_provider.get_credentials() + + return TokenAuthClientAuthResponse(_token, self._token_transport_header) diff --git a/chromadb/config.py b/chromadb/config.py index 87aa9e7..bc46114 100644 --- a/chromadb/config.py +++ b/chromadb/config.py @@ -140,6 +140,8 @@ class Settings(BaseSettings): # type: ignore ] = "chromadb.auth.providers.RequestsClientAuthProtocolAdapter" chroma_client_auth_credentials_file: Optional[str] = None chroma_client_auth_credentials: Optional[str] = None + chroma_client_auth_token_transport_header: Optional[str] = None + chroma_server_auth_token_transport_header: Optional[str] = None anonymized_telemetry: bool = True diff --git a/chromadb/test/auth/test_token_auth.py b/chromadb/test/auth/test_token_auth.py new file mode 100644 index 0000000..cb372ec --- /dev/null +++ b/chromadb/test/auth/test_token_auth.py @@ -0,0 +1,134 @@ +import string +from typing import Dict, Any + +import hypothesis.strategies as st +import pytest +from hypothesis import given, settings + +from chromadb.api import API +from chromadb.config import System +from chromadb.test.conftest import _fastapi_fixture + + +@st.composite +def token_config(draw: st.DrawFn) -> Dict[str, Any]: + token_header = draw(st.sampled_from(["AUTHORIZATION", "X_CHROMA_TOKEN", None])) + server_provider = draw( + st.sampled_from(["token", "chromadb.auth.token.TokenAuthServerProvider"]) + ) + client_provider = draw( + st.sampled_from(["token", "chromadb.auth.token.TokenAuthClientProvider"]) + ) + server_credentials_provider = draw( + st.sampled_from( + ["chromadb.auth.token.TokenConfigServerAuthCredentialsProvider"] + ) + ) + token = draw( + st.text(alphabet=string.ascii_letters + string.digits, min_size=1, max_size=50) + ) + persistence = draw(st.booleans()) + return { + "token_transport_header": token_header, + "chroma_server_auth_credentials": token, + "chroma_client_auth_credentials": token, + "chroma_server_auth_provider": server_provider, + "chroma_client_auth_provider": client_provider, + "chroma_server_auth_credentials_provider": server_credentials_provider, + "is_persistent": persistence, + } + + +@settings(max_examples=10) +@given(token_config()) +def test_fastapi_server_token_auth(token_config: Dict[str, Any]) -> None: + api = _fastapi_fixture( + is_persistent=token_config["is_persistent"], + chroma_server_auth_provider=token_config["chroma_server_auth_provider"], + chroma_server_auth_credentials_provider=token_config[ + "chroma_server_auth_credentials_provider" + ], + chroma_server_auth_credentials=token_config["chroma_server_auth_credentials"], + chroma_client_auth_provider=token_config["chroma_client_auth_provider"], + chroma_client_auth_token_transport_header=token_config[ + "token_transport_header" + ], + chroma_server_auth_token_transport_header=token_config[ + "token_transport_header" + ], + chroma_client_auth_credentials=token_config["chroma_client_auth_credentials"], + ) + _sys: System = next(api) + _sys.reset_state() + _api = _sys.instance(API) + _api.heartbeat() + assert _api.list_collections() == [] + + +@st.composite +def random_token(draw: st.DrawFn) -> str: + return draw( + st.text(alphabet=string.ascii_letters + string.digits, min_size=1, max_size=5) + ) + + +@st.composite +def invalid_token(draw: st.DrawFn) -> str: + opposite_alphabet = set(string.printable) - set( + string.ascii_letters + string.digits + ) + token = draw(st.text(alphabet=list(opposite_alphabet), min_size=1, max_size=50)) + return token + + +@settings(max_examples=10) +@given(tconf=token_config(), inval_tok=invalid_token()) +def test_invalid_token(tconf: Dict[str, Any], inval_tok: str) -> None: + api = _fastapi_fixture( + is_persistent=tconf["is_persistent"], + chroma_server_auth_provider=tconf["chroma_server_auth_provider"], + chroma_server_auth_credentials_provider=tconf[ + "chroma_server_auth_credentials_provider" + ], + chroma_server_auth_credentials=tconf["chroma_server_auth_credentials"], + chroma_server_auth_token_transport_header=tconf["token_transport_header"], + chroma_client_auth_provider=tconf["chroma_client_auth_provider"], + chroma_client_auth_token_transport_header=tconf["token_transport_header"], + chroma_client_auth_credentials=inval_tok, + ) + with pytest.raises(Exception) as e: + _sys: System = next(api) + _sys.reset_state() + _sys.instance(API) + assert "Invalid token" in str(e) + + +@settings(max_examples=10) +@given(token_config(), random_token()) +def test_fastapi_server_token_auth_wrong_token( + token_config: Dict[str, Any], random_token: str +) -> None: + api = _fastapi_fixture( + is_persistent=token_config["is_persistent"], + chroma_server_auth_provider=token_config["chroma_server_auth_provider"], + chroma_server_auth_credentials_provider=token_config[ + "chroma_server_auth_credentials_provider" + ], + chroma_server_auth_credentials=token_config["chroma_server_auth_credentials"], + chroma_server_auth_token_transport_header=token_config[ + "token_transport_header" + ], + chroma_client_auth_provider=token_config["chroma_client_auth_provider"], + chroma_client_auth_token_transport_header=token_config[ + "token_transport_header" + ], + chroma_client_auth_credentials=token_config["chroma_client_auth_credentials"] + + random_token, + ) + _sys: System = next(api) + _sys.reset_state() + _api = _sys.instance(API) + _api.heartbeat() + with pytest.raises(Exception) as e: + _api.list_collections() + assert "Unauthorized" in str(e) diff --git a/chromadb/test/conftest.py b/chromadb/test/conftest.py index 5ec6cdf..fe91622 100644 --- a/chromadb/test/conftest.py +++ b/chromadb/test/conftest.py @@ -24,9 +24,9 @@ from typing_extensions import Protocol import chromadb.server.fastapi from chromadb.api import API from chromadb.config import Settings, System +from chromadb.db.mixins import embeddings_queue from chromadb.ingest import Producer from chromadb.types import SeqId, SubmitEmbeddingRecord -from chromadb.db.mixins import embeddings_queue root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG) # This will only run when testing @@ -59,6 +59,8 @@ def _run_server( chroma_server_auth_provider: Optional[str] = None, chroma_server_auth_credentials_provider: Optional[str] = None, chroma_server_auth_credentials_file: Optional[str] = None, + chroma_server_auth_credentials: Optional[str] = None, + chroma_server_auth_token_transport_header: Optional[str] = None, ) -> None: """Run a Chroma server locally""" if is_persistent and persist_directory: @@ -74,6 +76,8 @@ def _run_server( chroma_server_auth_provider=chroma_server_auth_provider, chroma_server_auth_credentials_provider=chroma_server_auth_credentials_provider, chroma_server_auth_credentials_file=chroma_server_auth_credentials_file, + chroma_server_auth_credentials=chroma_server_auth_credentials, + chroma_server_auth_token_transport_header=chroma_server_auth_token_transport_header, ) else: settings = Settings( @@ -87,6 +91,8 @@ def _run_server( chroma_server_auth_provider=chroma_server_auth_provider, chroma_server_auth_credentials_provider=chroma_server_auth_credentials_provider, chroma_server_auth_credentials_file=chroma_server_auth_credentials_file, + chroma_server_auth_credentials=chroma_server_auth_credentials, + chroma_server_auth_token_transport_header=chroma_server_auth_token_transport_header, ) server = chromadb.server.fastapi.FastAPI(settings) uvicorn.run(server.app(), host="0.0.0.0", port=port, log_level="error") @@ -112,6 +118,9 @@ def _fastapi_fixture( chroma_client_auth_provider: Optional[str] = None, chroma_server_auth_credentials_file: Optional[str] = None, chroma_client_auth_credentials: Optional[str] = None, + chroma_server_auth_credentials: Optional[str] = None, + chroma_client_auth_token_transport_header: Optional[str] = None, + chroma_server_auth_token_transport_header: Optional[str] = None, ) -> Generator[System, None, None]: """Fixture generator that launches a server in a separate process, and yields a fastapi client connect to it""" @@ -120,7 +129,14 @@ def _fastapi_fixture( logger.info(f"Running test FastAPI server on port {port}") ctx = multiprocessing.get_context("spawn") args: Tuple[ - int, bool, Optional[str], Optional[str], Optional[str], Optional[str] + int, + bool, + Optional[str], + Optional[str], + Optional[str], + Optional[str], + Optional[str], + Optional[str], ] = ( port, False, @@ -128,6 +144,8 @@ def _fastapi_fixture( chroma_server_auth_provider, chroma_server_auth_credentials_provider, chroma_server_auth_credentials_file, + chroma_server_auth_credentials, + chroma_server_auth_token_transport_header, ) persist_directory = None if is_persistent: @@ -139,6 +157,8 @@ def _fastapi_fixture( chroma_server_auth_provider, chroma_server_auth_credentials_provider, chroma_server_auth_credentials_file, + chroma_server_auth_credentials, + chroma_server_auth_token_transport_header, ) proc = ctx.Process(target=_run_server, args=args, daemon=True) proc.start() @@ -149,6 +169,7 @@ def _fastapi_fixture( allow_reset=True, chroma_client_auth_provider=chroma_client_auth_provider, chroma_client_auth_credentials=chroma_client_auth_credentials, + chroma_client_auth_token_transport_header=chroma_client_auth_token_transport_header, ) system = System(settings) api = system.instance(API) @@ -170,7 +191,7 @@ def fastapi_persistent() -> Generator[System, None, None]: return _fastapi_fixture(is_persistent=True) -def fastapi_server_auth() -> Generator[System, None, None]: +def fastapi_server_basic_auth() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -186,7 +207,7 @@ def fastapi_server_auth() -> Generator[System, None, None]: os.remove(server_auth_file) -def fastapi_server_auth_param() -> Generator[System, None, None]: +def fastapi_server_basic_auth_param() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -203,7 +224,7 @@ def fastapi_server_auth_param() -> Generator[System, None, None]: # TODO we need a generator for auth providers -def fastapi_server_auth_file() -> Generator[System, None, None]: +def fastapi_server_basic_auth_file() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -219,7 +240,7 @@ def fastapi_server_auth_file() -> Generator[System, None, None]: os.remove(server_auth_file) -def fastapi_server_auth_shorthand() -> Generator[System, None, None]: +def fastapi_server_basic_auth_shorthand() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -235,8 +256,7 @@ def fastapi_server_auth_shorthand() -> Generator[System, None, None]: os.remove(server_auth_file) -@pytest.fixture(scope="function") -def fastapi_server_auth_invalid_cred() -> Generator[System, None, None]: +def fastapi_server_basic_auth_invalid_cred() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -312,13 +332,23 @@ def system_fixtures() -> List[Callable[[], Generator[System, None, None]]]: def system_fixtures_auth() -> List[Callable[[], Generator[System, None, None]]]: fixtures = [ - fastapi_server_auth_param, - fastapi_server_auth_file, - fastapi_server_auth_shorthand, + fastapi_server_basic_auth_param, + fastapi_server_basic_auth_file, + fastapi_server_basic_auth_shorthand, ] return fixtures +def system_fixtures_wrong_auth() -> List[Callable[[], Generator[System, None, None]]]: + fixtures = [fastapi_server_basic_auth_invalid_cred] + return fixtures + + +@pytest.fixture(scope="module", params=system_fixtures_wrong_auth()) +def system_wrong_auth(request: pytest.FixtureRequest) -> Generator[API, None, None]: + yield next(request.param()) + + @pytest.fixture(scope="module", params=system_fixtures()) def system(request: pytest.FixtureRequest) -> Generator[API, None, None]: yield next(request.param()) @@ -338,10 +368,10 @@ def api(system: System) -> Generator[API, None, None]: @pytest.fixture(scope="function") def api_wrong_cred( - fastapi_server_auth_invalid_cred: System, + system_wrong_auth: System, ) -> Generator[API, None, None]: - fastapi_server_auth_invalid_cred.reset_state() - api = fastapi_server_auth_invalid_cred.instance(API) + system_wrong_auth.reset_state() + api = system_wrong_auth.instance(API) yield api diff --git a/docker-compose.yml b/docker-compose.yml index ba95516..1d78773 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - IS_PERSISTENT=TRUE - CHROMA_SERVER_AUTH_PROVIDER=${CHROMA_SERVER_AUTH_PROVIDER} - CHROMA_SERVER_AUTH_CREDENTIALS_FILE=${CHROMA_SERVER_AUTH_CREDENTIALS_FILE} + - CHROMA_SERVER_AUTH_CREDENTIALS=${CHROMA_SERVER_AUTH_CREDENTIALS} - CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=${CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER} ports: - 8000:8000 diff --git a/examples/basic_functionality/client_auth.ipynb b/examples/basic_functionality/client_auth.ipynb index 0fdb82e..b856a9f 100644 --- a/examples/basic_functionality/client_auth.ipynb +++ b/examples/basic_functionality/client_auth.ipynb @@ -135,7 +135,7 @@ { "cell_type": "markdown", "source": [ - "### Basic Authentication" + "## Basic Authentication" ], "metadata": { "collapsed": false @@ -160,7 +160,8 @@ "from chromadb import Settings\n", "\n", "client = chromadb.HttpClient(\n", - " settings=Settings(chroma_client_auth_provider=\"chromadb.auth.basic.BasicAuthClientProvider\",chroma_client_auth_credentials=\"admin:admin\"))\n", + " settings=Settings(chroma_client_auth_provider=\"chromadb.auth.basic.BasicAuthClientProvider\",\n", + " chroma_client_auth_credentials=\"admin:admin\"))\n", "client.heartbeat() # this should work with or without authentication - it is a public endpoint\n", "\n", "client.get_version() # this should work with or without authentication - it is a public endpoint\n", @@ -221,18 +222,152 @@ }, "id": "c0c3240ed4d70a79" }, + { + "cell_type": "markdown", + "source": [ + "## Token Authentication\n", + "\n", + "> Note: Tokens must be valid ASCII strings.\n", + "\n", + "### Default Token (`Authorization` with `Bearer`)" + ], + "metadata": { + "collapsed": false + }, + "id": "390aed41f019649b" + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [ + { + "ename": "ConnectionError", + "evalue": "HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /api/v1 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mConnectionRefusedError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connection.py:174\u001B[0m, in \u001B[0;36mHTTPConnection._new_conn\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 173\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 174\u001B[0m conn \u001B[38;5;241m=\u001B[39m \u001B[43mconnection\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcreate_connection\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 175\u001B[0m \u001B[43m \u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_dns_host\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mport\u001B[49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtimeout\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mextra_kw\u001B[49m\n\u001B[1;32m 176\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 178\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m SocketTimeout:\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/util/connection.py:95\u001B[0m, in \u001B[0;36mcreate_connection\u001B[0;34m(address, timeout, source_address, socket_options)\u001B[0m\n\u001B[1;32m 94\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m err \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[0;32m---> 95\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m err\n\u001B[1;32m 97\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m socket\u001B[38;5;241m.\u001B[39merror(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mgetaddrinfo returns an empty list\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/util/connection.py:85\u001B[0m, in \u001B[0;36mcreate_connection\u001B[0;34m(address, timeout, source_address, socket_options)\u001B[0m\n\u001B[1;32m 84\u001B[0m sock\u001B[38;5;241m.\u001B[39mbind(source_address)\n\u001B[0;32m---> 85\u001B[0m \u001B[43msock\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mconnect\u001B[49m\u001B[43m(\u001B[49m\u001B[43msa\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 86\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m sock\n", + "\u001B[0;31mConnectionRefusedError\u001B[0m: [Errno 61] Connection refused", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mNewConnectionError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connectionpool.py:714\u001B[0m, in \u001B[0;36mHTTPConnectionPool.urlopen\u001B[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001B[0m\n\u001B[1;32m 713\u001B[0m \u001B[38;5;66;03m# Make the request on the httplib connection object.\u001B[39;00m\n\u001B[0;32m--> 714\u001B[0m httplib_response \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_make_request\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 715\u001B[0m \u001B[43m \u001B[49m\u001B[43mconn\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 716\u001B[0m \u001B[43m \u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 717\u001B[0m \u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 718\u001B[0m \u001B[43m \u001B[49m\u001B[43mtimeout\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtimeout_obj\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 719\u001B[0m \u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 720\u001B[0m \u001B[43m \u001B[49m\u001B[43mheaders\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mheaders\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 721\u001B[0m \u001B[43m \u001B[49m\u001B[43mchunked\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mchunked\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 722\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 724\u001B[0m \u001B[38;5;66;03m# If we're going to release the connection in ``finally:``, then\u001B[39;00m\n\u001B[1;32m 725\u001B[0m \u001B[38;5;66;03m# the response doesn't need to know about the connection. Otherwise\u001B[39;00m\n\u001B[1;32m 726\u001B[0m \u001B[38;5;66;03m# it will also try to release it and we'll have a double-release\u001B[39;00m\n\u001B[1;32m 727\u001B[0m \u001B[38;5;66;03m# mess.\u001B[39;00m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connectionpool.py:415\u001B[0m, in \u001B[0;36mHTTPConnectionPool._make_request\u001B[0;34m(self, conn, method, url, timeout, chunked, **httplib_request_kw)\u001B[0m\n\u001B[1;32m 414\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m--> 415\u001B[0m \u001B[43mconn\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mhttplib_request_kw\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 417\u001B[0m \u001B[38;5;66;03m# We are swallowing BrokenPipeError (errno.EPIPE) since the server is\u001B[39;00m\n\u001B[1;32m 418\u001B[0m \u001B[38;5;66;03m# legitimately able to close the connection after sending a valid response.\u001B[39;00m\n\u001B[1;32m 419\u001B[0m \u001B[38;5;66;03m# With this behaviour, the received response is still readable.\u001B[39;00m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connection.py:244\u001B[0m, in \u001B[0;36mHTTPConnection.request\u001B[0;34m(self, method, url, body, headers)\u001B[0m\n\u001B[1;32m 243\u001B[0m headers[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mUser-Agent\u001B[39m\u001B[38;5;124m\"\u001B[39m] \u001B[38;5;241m=\u001B[39m _get_default_user_agent()\n\u001B[0;32m--> 244\u001B[0m \u001B[38;5;28;43msuper\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43mHTTPConnection\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mheaders\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mheaders\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:1282\u001B[0m, in \u001B[0;36mHTTPConnection.request\u001B[0;34m(self, method, url, body, headers, encode_chunked)\u001B[0m\n\u001B[1;32m 1281\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Send a complete request to the server.\"\"\"\u001B[39;00m\n\u001B[0;32m-> 1282\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_send_request\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mheaders\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mencode_chunked\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:1328\u001B[0m, in \u001B[0;36mHTTPConnection._send_request\u001B[0;34m(self, method, url, body, headers, encode_chunked)\u001B[0m\n\u001B[1;32m 1327\u001B[0m body \u001B[38;5;241m=\u001B[39m _encode(body, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mbody\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[0;32m-> 1328\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mendheaders\u001B[49m\u001B[43m(\u001B[49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mencode_chunked\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mencode_chunked\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:1277\u001B[0m, in \u001B[0;36mHTTPConnection.endheaders\u001B[0;34m(self, message_body, encode_chunked)\u001B[0m\n\u001B[1;32m 1276\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m CannotSendHeader()\n\u001B[0;32m-> 1277\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_send_output\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmessage_body\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mencode_chunked\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mencode_chunked\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:1037\u001B[0m, in \u001B[0;36mHTTPConnection._send_output\u001B[0;34m(self, message_body, encode_chunked)\u001B[0m\n\u001B[1;32m 1036\u001B[0m \u001B[38;5;28;01mdel\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_buffer[:]\n\u001B[0;32m-> 1037\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmsg\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 1039\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m message_body \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m 1040\u001B[0m \n\u001B[1;32m 1041\u001B[0m \u001B[38;5;66;03m# create a consistent interface to message_body\u001B[39;00m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:975\u001B[0m, in \u001B[0;36mHTTPConnection.send\u001B[0;34m(self, data)\u001B[0m\n\u001B[1;32m 974\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mauto_open:\n\u001B[0;32m--> 975\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mconnect\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 976\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connection.py:205\u001B[0m, in \u001B[0;36mHTTPConnection.connect\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 204\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mconnect\u001B[39m(\u001B[38;5;28mself\u001B[39m):\n\u001B[0;32m--> 205\u001B[0m conn \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_new_conn\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 206\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_prepare_conn(conn)\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connection.py:186\u001B[0m, in \u001B[0;36mHTTPConnection._new_conn\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 185\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m SocketError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[0;32m--> 186\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m NewConnectionError(\n\u001B[1;32m 187\u001B[0m \u001B[38;5;28mself\u001B[39m, \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mFailed to establish a new connection: \u001B[39m\u001B[38;5;132;01m%s\u001B[39;00m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;241m%\u001B[39m e\n\u001B[1;32m 188\u001B[0m )\n\u001B[1;32m 190\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m conn\n", + "\u001B[0;31mNewConnectionError\u001B[0m: : Failed to establish a new connection: [Errno 61] Connection refused", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mMaxRetryError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/adapters.py:489\u001B[0m, in \u001B[0;36mHTTPAdapter.send\u001B[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001B[0m\n\u001B[1;32m 488\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m chunked:\n\u001B[0;32m--> 489\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[43mconn\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43murlopen\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 490\u001B[0m \u001B[43m \u001B[49m\u001B[43mmethod\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 491\u001B[0m \u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 492\u001B[0m \u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 493\u001B[0m \u001B[43m \u001B[49m\u001B[43mheaders\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mheaders\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 494\u001B[0m \u001B[43m \u001B[49m\u001B[43mredirect\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[1;32m 495\u001B[0m \u001B[43m \u001B[49m\u001B[43massert_same_host\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[1;32m 496\u001B[0m \u001B[43m \u001B[49m\u001B[43mpreload_content\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[1;32m 497\u001B[0m \u001B[43m \u001B[49m\u001B[43mdecode_content\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[1;32m 498\u001B[0m \u001B[43m \u001B[49m\u001B[43mretries\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmax_retries\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 499\u001B[0m \u001B[43m \u001B[49m\u001B[43mtimeout\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtimeout\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 500\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 502\u001B[0m \u001B[38;5;66;03m# Send the request.\u001B[39;00m\n\u001B[1;32m 503\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connectionpool.py:798\u001B[0m, in \u001B[0;36mHTTPConnectionPool.urlopen\u001B[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001B[0m\n\u001B[1;32m 796\u001B[0m e \u001B[38;5;241m=\u001B[39m ProtocolError(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mConnection aborted.\u001B[39m\u001B[38;5;124m\"\u001B[39m, e)\n\u001B[0;32m--> 798\u001B[0m retries \u001B[38;5;241m=\u001B[39m \u001B[43mretries\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mincrement\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 799\u001B[0m \u001B[43m \u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43merror\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43me\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43m_pool\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43m_stacktrace\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43msys\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mexc_info\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m[\u001B[49m\u001B[38;5;241;43m2\u001B[39;49m\u001B[43m]\u001B[49m\n\u001B[1;32m 800\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 801\u001B[0m retries\u001B[38;5;241m.\u001B[39msleep()\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/util/retry.py:592\u001B[0m, in \u001B[0;36mRetry.increment\u001B[0;34m(self, method, url, response, error, _pool, _stacktrace)\u001B[0m\n\u001B[1;32m 591\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m new_retry\u001B[38;5;241m.\u001B[39mis_exhausted():\n\u001B[0;32m--> 592\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m MaxRetryError(_pool, url, error \u001B[38;5;129;01mor\u001B[39;00m ResponseError(cause))\n\u001B[1;32m 594\u001B[0m log\u001B[38;5;241m.\u001B[39mdebug(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mIncremented Retry for (url=\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;132;01m%s\u001B[39;00m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m): \u001B[39m\u001B[38;5;132;01m%r\u001B[39;00m\u001B[38;5;124m\"\u001B[39m, url, new_retry)\n", + "\u001B[0;31mMaxRetryError\u001B[0m: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /api/v1 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mConnectionError\u001B[0m Traceback (most recent call last)", + "Cell \u001B[0;32mIn[3], line 6\u001B[0m\n\u001B[1;32m 2\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mchromadb\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m Settings\n\u001B[1;32m 4\u001B[0m client \u001B[38;5;241m=\u001B[39m chromadb\u001B[38;5;241m.\u001B[39mHttpClient(\n\u001B[1;32m 5\u001B[0m settings\u001B[38;5;241m=\u001B[39mSettings(chroma_client_auth_provider\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mtoken\u001B[39m\u001B[38;5;124m\"\u001B[39m, chroma_client_auth_credentials\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mtest-token\u001B[39m\u001B[38;5;124m\"\u001B[39m))\n\u001B[0;32m----> 6\u001B[0m \u001B[43mclient\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mheartbeat\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m \u001B[38;5;66;03m# this should work with or without authentication - it is a public endpoint\u001B[39;00m\n\u001B[1;32m 8\u001B[0m client\u001B[38;5;241m.\u001B[39mget_version() \u001B[38;5;66;03m# this should work with or without authentication - it is a public endpoint\u001B[39;00m\n\u001B[1;32m 10\u001B[0m client\u001B[38;5;241m.\u001B[39mlist_collections() \u001B[38;5;66;03m# this is a protected endpoint and requires authentication\u001B[39;00m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/api/fastapi.py:84\u001B[0m, in \u001B[0;36mFastAPI.heartbeat\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 81\u001B[0m \u001B[38;5;129m@override\u001B[39m\n\u001B[1;32m 82\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mheartbeat\u001B[39m(\u001B[38;5;28mself\u001B[39m) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28mint\u001B[39m:\n\u001B[1;32m 83\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Returns the current server time in nanoseconds to check if the server is alive\"\"\"\u001B[39;00m\n\u001B[0;32m---> 84\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_session\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mget\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_api_url\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 85\u001B[0m raise_chroma_error(resp)\n\u001B[1;32m 86\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mint\u001B[39m(resp\u001B[38;5;241m.\u001B[39mjson()[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mnanosecond heartbeat\u001B[39m\u001B[38;5;124m\"\u001B[39m])\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/sessions.py:600\u001B[0m, in \u001B[0;36mSession.get\u001B[0;34m(self, url, **kwargs)\u001B[0m\n\u001B[1;32m 592\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124mr\u001B[39m\u001B[38;5;124;03m\"\"\"Sends a GET request. Returns :class:`Response` object.\u001B[39;00m\n\u001B[1;32m 593\u001B[0m \n\u001B[1;32m 594\u001B[0m \u001B[38;5;124;03m:param url: URL for the new :class:`Request` object.\u001B[39;00m\n\u001B[1;32m 595\u001B[0m \u001B[38;5;124;03m:param \\*\\*kwargs: Optional arguments that ``request`` takes.\u001B[39;00m\n\u001B[1;32m 596\u001B[0m \u001B[38;5;124;03m:rtype: requests.Response\u001B[39;00m\n\u001B[1;32m 597\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 599\u001B[0m kwargs\u001B[38;5;241m.\u001B[39msetdefault(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mallow_redirects\u001B[39m\u001B[38;5;124m\"\u001B[39m, \u001B[38;5;28;01mTrue\u001B[39;00m)\n\u001B[0;32m--> 600\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mGET\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/sessions.py:587\u001B[0m, in \u001B[0;36mSession.request\u001B[0;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001B[0m\n\u001B[1;32m 582\u001B[0m send_kwargs \u001B[38;5;241m=\u001B[39m {\n\u001B[1;32m 583\u001B[0m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mtimeout\u001B[39m\u001B[38;5;124m\"\u001B[39m: timeout,\n\u001B[1;32m 584\u001B[0m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mallow_redirects\u001B[39m\u001B[38;5;124m\"\u001B[39m: allow_redirects,\n\u001B[1;32m 585\u001B[0m }\n\u001B[1;32m 586\u001B[0m send_kwargs\u001B[38;5;241m.\u001B[39mupdate(settings)\n\u001B[0;32m--> 587\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mprep\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43msend_kwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 589\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m resp\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/auth/providers.py:123\u001B[0m, in \u001B[0;36mRequestsClientAuthProtocolAdapter._Session.send\u001B[0;34m(self, request, **kwargs)\u001B[0m\n\u001B[1;32m 118\u001B[0m \u001B[38;5;129m@override\u001B[39m\n\u001B[1;32m 119\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21msend\u001B[39m(\n\u001B[1;32m 120\u001B[0m \u001B[38;5;28mself\u001B[39m, request: requests\u001B[38;5;241m.\u001B[39mPreparedRequest, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs: Any\n\u001B[1;32m 121\u001B[0m ) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m requests\u001B[38;5;241m.\u001B[39mResponse:\n\u001B[1;32m 122\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_protocol_adapter\u001B[38;5;241m.\u001B[39minject_credentials(request)\n\u001B[0;32m--> 123\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43msuper\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mrequest\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/sessions.py:701\u001B[0m, in \u001B[0;36mSession.send\u001B[0;34m(self, request, **kwargs)\u001B[0m\n\u001B[1;32m 698\u001B[0m start \u001B[38;5;241m=\u001B[39m preferred_clock()\n\u001B[1;32m 700\u001B[0m \u001B[38;5;66;03m# Send the request\u001B[39;00m\n\u001B[0;32m--> 701\u001B[0m r \u001B[38;5;241m=\u001B[39m \u001B[43madapter\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mrequest\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 703\u001B[0m \u001B[38;5;66;03m# Total elapsed time of the request (approximately)\u001B[39;00m\n\u001B[1;32m 704\u001B[0m elapsed \u001B[38;5;241m=\u001B[39m preferred_clock() \u001B[38;5;241m-\u001B[39m start\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/adapters.py:565\u001B[0m, in \u001B[0;36mHTTPAdapter.send\u001B[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001B[0m\n\u001B[1;32m 561\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28misinstance\u001B[39m(e\u001B[38;5;241m.\u001B[39mreason, _SSLError):\n\u001B[1;32m 562\u001B[0m \u001B[38;5;66;03m# This branch is for urllib3 v1.22 and later.\u001B[39;00m\n\u001B[1;32m 563\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m SSLError(e, request\u001B[38;5;241m=\u001B[39mrequest)\n\u001B[0;32m--> 565\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mConnectionError\u001B[39;00m(e, request\u001B[38;5;241m=\u001B[39mrequest)\n\u001B[1;32m 567\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m ClosedPoolError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[1;32m 568\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mConnectionError\u001B[39;00m(e, request\u001B[38;5;241m=\u001B[39mrequest)\n", + "\u001B[0;31mConnectionError\u001B[0m: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /api/v1 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))" + ] + } + ], + "source": [ + "import chromadb\n", + "from chromadb import Settings\n", + "\n", + "client = chromadb.HttpClient(\n", + " settings=Settings(chroma_client_auth_provider=\"token\", chroma_client_auth_credentials=\"test-token\"))\n", + "client.heartbeat() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.get_version() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.list_collections() # this is a protected endpoint and requires authentication\n" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-08-28T16:44:30.289045Z", + "start_time": "2023-08-28T16:44:29.878090Z" + } + }, + "id": "b218beb03ae1582e" + }, + { + "cell_type": "markdown", + "source": [ + "### X-Chroma-Token" + ], + "metadata": { + "collapsed": false + }, + "id": "c8234687c5afe521" + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [ + { + "ename": "Exception", + "evalue": "{\"error\":\"Unauthorized\"}", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mHTTPError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/api/fastapi.py:410\u001B[0m, in \u001B[0;36mraise_chroma_error\u001B[0;34m(resp)\u001B[0m\n\u001B[1;32m 409\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 410\u001B[0m \u001B[43mresp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mraise_for_status\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 411\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m requests\u001B[38;5;241m.\u001B[39mHTTPError:\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/models.py:1021\u001B[0m, in \u001B[0;36mResponse.raise_for_status\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 1020\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m http_error_msg:\n\u001B[0;32m-> 1021\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m HTTPError(http_error_msg, response\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m)\n", + "\u001B[0;31mHTTPError\u001B[0m: 401 Client Error: Unauthorized for url: http://localhost:8000/api/v1/collections", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mException\u001B[0m Traceback (most recent call last)", + "Cell \u001B[0;32mIn[2], line 11\u001B[0m\n\u001B[1;32m 7\u001B[0m client\u001B[38;5;241m.\u001B[39mheartbeat() \u001B[38;5;66;03m# this should work with or without authentication - it is a public endpoint\u001B[39;00m\n\u001B[1;32m 9\u001B[0m client\u001B[38;5;241m.\u001B[39mget_version() \u001B[38;5;66;03m# this should work with or without authentication - it is a public endpoint\u001B[39;00m\n\u001B[0;32m---> 11\u001B[0m \u001B[43mclient\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mlist_collections\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m \u001B[38;5;66;03m# this is a protected endpoint and requires authentication\u001B[39;00m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/api/fastapi.py:92\u001B[0m, in \u001B[0;36mFastAPI.list_collections\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 90\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Returns a list of all collections\"\"\"\u001B[39;00m\n\u001B[1;32m 91\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_session\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_api_url \u001B[38;5;241m+\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m/collections\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[0;32m---> 92\u001B[0m \u001B[43mraise_chroma_error\u001B[49m\u001B[43m(\u001B[49m\u001B[43mresp\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 93\u001B[0m json_collections \u001B[38;5;241m=\u001B[39m resp\u001B[38;5;241m.\u001B[39mjson()\n\u001B[1;32m 94\u001B[0m collections \u001B[38;5;241m=\u001B[39m []\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/api/fastapi.py:412\u001B[0m, in \u001B[0;36mraise_chroma_error\u001B[0;34m(resp)\u001B[0m\n\u001B[1;32m 410\u001B[0m resp\u001B[38;5;241m.\u001B[39mraise_for_status()\n\u001B[1;32m 411\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m requests\u001B[38;5;241m.\u001B[39mHTTPError:\n\u001B[0;32m--> 412\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m (\u001B[38;5;167;01mException\u001B[39;00m(resp\u001B[38;5;241m.\u001B[39mtext))\n", + "\u001B[0;31mException\u001B[0m: {\"error\":\"Unauthorized\"}" + ] + } + ], + "source": [ + "import chromadb\n", + "from chromadb import Settings\n", + "\n", + "client = chromadb.HttpClient(\n", + " settings=Settings(chroma_client_auth_provider=\"token\", chroma_client_auth_credentials=\"test-token\",\n", + " chroma_client_auth_token_transport_header=\"X_CHROMA_TOKEN\"))\n", + "client.heartbeat() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.get_version() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.list_collections() # this is a protected endpoint and requires authentication" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-08-28T14:25:12.858416Z", + "start_time": "2023-08-28T14:25:12.629618Z" + } + }, + "id": "93485c3175d1e2c7" + }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [], "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-08-11T15:58:07.272237Z" - } + "collapsed": false }, - "id": "ab8b90d83f02eda" + "id": "29d28a25e85f95af" } ], "metadata": {