diff --git a/models.py b/models.py index 6306148..27cb299 100644 --- a/models.py +++ b/models.py @@ -1,7 +1,13 @@ from flask_sqlalchemy import SQLAlchemy from datetime import datetime, timedelta -import tiktoken +import os +try: + import tiktoken + TIKTOKEN_AVAILABLE = True +except Exception: + TIKTOKEN_AVAILABLE = False + tiktoken = None import json @@ -119,9 +125,16 @@ class Message(db.Model): if self.token_count is None: if self.is_base64_image(): self.token_count = 0 + elif not TIKTOKEN_AVAILABLE or os.environ.get("TESTING"): + # Fallback: estimate ~4 chars per token when tiktoken unavailable + self.token_count = len(self.content) // 4 + 1 else: - encoding = tiktoken.encoding_for_model("gpt-4") - self.token_count = len(encoding.encode(self.content)) + try: + encoding = tiktoken.encoding_for_model("gpt-4") + self.token_count = len(encoding.encode(self.content)) + except Exception: + # Fallback on any tiktoken error (network, SSL, etc.) + self.token_count = len(self.content) // 4 + 1 return self.token_count def is_base64_image(self): diff --git a/tests/conftest.py b/tests/conftest.py index d24fad1..da03738 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,59 @@ pytest configuration and fixtures for OpenCompletion testing Sets up common test environment variables and fixtures used across all tests. """ +import sys import os + +# ============================================================================= +# CRITICAL: Mock tiktoken BEFORE any other imports +# tiktoken tries to download encoding files over HTTPS which conflicts +# with gevent's monkey-patching of SSL, causing RecursionError +# ============================================================================= + + +class MockTiktokenEncoding: + """Mock tiktoken encoding that doesn't make network requests""" + def encode(self, text): + # Simple approximation: ~4 chars per token + return list(range(len(text) // 4 + 1)) + + +class MockTiktoken: + """Mock tiktoken module""" + _encoding = MockTiktokenEncoding() + + @staticmethod + def encoding_for_model(model_name): + return MockTiktoken._encoding + + @staticmethod + def get_encoding(encoding_name): + return MockTiktoken._encoding + + +# Insert mock tiktoken into sys.modules BEFORE any imports +if 'tiktoken' not in sys.modules: + sys.modules['tiktoken'] = MockTiktoken() + + +def pytest_configure(config): + """ + Called early in pytest startup, before test collection. + Ensures tiktoken is mocked before any test imports happen. + """ + if 'tiktoken' not in sys.modules: + sys.modules['tiktoken'] = MockTiktoken() + else: + # If tiktoken was already imported, patch its functions + import tiktoken + tiktoken.encoding_for_model = MockTiktoken.encoding_for_model + tiktoken.get_encoding = MockTiktoken.get_encoding + + +# ============================================================================= +# Now safe to do other imports +# ============================================================================= + import pytest import tempfile from unittest.mock import patch, MagicMock