Skip to content

Commit

Permalink
feat: Improve configuration management and connection logging
Browse files Browse the repository at this point in the history
- Remove default values for connection parameters
- Add connection logging to show which instance we're connecting to
- Improve configuration validation
- Fix SurrealDB client method names
- Add better error messages for missing configuration

BREAKING CHANGE: Users must now explicitly provide all connection parameters
  • Loading branch information
lfnovo committed Jan 3, 2025
1 parent dc91954 commit a4d584c
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 72 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ jobs:
uv pip install -e ".[dev,all]"
- name: Run tests
run: uv run pytest -v
run: uv run pytest -v -m "not integration"
118 changes: 73 additions & 45 deletions src/surrantic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
setup_logging(logging.DEBUG)

# Default database configuration
SURREAL_ADDRESS = os.getenv("SURREAL_ADDRESS", "ws://localhost:8000")
SURREAL_USER = os.getenv("SURREAL_USER", "root")
SURREAL_PASS = os.getenv("SURREAL_PASS", "root")
SURREAL_NAMESPACE = os.getenv("SURREAL_NAMESPACE", "test")
SURREAL_DATABASE = os.getenv("SURREAL_DATABASE", "test")
SURREAL_ADDRESS = os.getenv("SURREAL_ADDRESS")
SURREAL_USER = os.getenv("SURREAL_USER")
SURREAL_PASS = os.getenv("SURREAL_PASS")
SURREAL_NAMESPACE = os.getenv("SURREAL_NAMESPACE")
SURREAL_DATABASE = os.getenv("SURREAL_DATABASE")

class SurranticConfig:
"""Configuration class for Surrantic database connection.
Expand All @@ -40,50 +40,80 @@ def __init__(self):
self.namespace = SURREAL_NAMESPACE
self.database = SURREAL_DATABASE
self.debug = False

@classmethod
def get_instance(cls) -> 'SurranticConfig':
"""Get the singleton instance of SurranticConfig"""
if cls._instance is None:
cls._instance = cls()
if not cls._instance:
cls._instance = cls.__new__(cls)
cls._instance.address = None
cls._instance.user = None
cls._instance.password = None
cls._instance.namespace = None
cls._instance.database = None
cls._instance.debug = False

# Validate configuration
if not all([
cls._instance.address,
cls._instance.user,
cls._instance.password,
cls._instance.namespace,
cls._instance.database
]):
raise ValueError(
"Missing required configuration. Please set all required options: "
"address, user, password, namespace, database"
)

return cls._instance

@classmethod
def reset(cls) -> None:
"""Reset the configuration to default values."""
cls._instance = None

@classmethod
def configure(cls,
address: Optional[str] = None,
user: Optional[str] = None,
password: Optional[str] = None,
namespace: Optional[str] = None,
database: Optional[str] = None,
debug: Optional[bool] = None) -> None:
"""Configure the database connection parameters.
def configure(cls, **kwargs) -> None:
"""Configure the database connection.
Args:
address: The SurrealDB server address
user: The username for authentication
password: The password for authentication
namespace: The namespace to use
database: The database to use
debug: If True, all queries and results will be logged
**kwargs: Configuration options to override. Valid options are:
address (str): Database address
user (str): Database user
password (str): Database password
namespace (str): Database namespace
database (str): Database name
debug (bool): Enable debug mode
"""
config = cls.get_instance()
if address is not None:
config.address = address
if user is not None:
config.user = user
if password is not None:
config.password = password
if namespace is not None:
config.namespace = namespace
if database is not None:
config.database = database
if debug is not None:
config.debug = debug
if not cls._instance:
cls._instance = cls.__new__(cls)
cls._instance.address = None
cls._instance.user = None
cls._instance.password = None
cls._instance.namespace = None
cls._instance.database = None
cls._instance.debug = False

# Update configuration
for key, value in kwargs.items():
if hasattr(cls._instance, key):
setattr(cls._instance, key, value)
else:
raise ValueError(f"Invalid configuration option: {key}")

# Validate configuration
if not all([
cls._instance.address,
cls._instance.user,
cls._instance.password,
cls._instance.namespace,
cls._instance.database
]):
raise ValueError(
"Missing required configuration. Please set all required options: "
"address, user, password, namespace, database"
)

@classmethod
def reset(cls) -> None:
"""Reset configuration to default values."""
cls._instance = None

T = TypeVar("T", bound="ObjectModel")
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -163,13 +193,12 @@ async def _get_db(cls) -> AsyncGenerator[AsyncSurrealDB, None]:
config = SurranticConfig.get_instance()
db = AsyncSurrealDB(url=config.address)
try:
logger.info(f"Connecting to SurrealDB at {config.address}")
await db.connect()
await db.sign_in(config.user, config.password)
await db.use(config.namespace, config.database)
_log_query("Database connection established")
yield db
finally:
_log_query("Database connection closed")
await db.close()

@classmethod
Expand All @@ -183,13 +212,12 @@ def _get_sync_db(cls) -> Generator[SurrealDB, None, None]:
config = SurranticConfig.get_instance()
db = SurrealDB(url=config.address)
try:
logger.info(f"Connecting to SurrealDB at {config.address}")
db.connect()
db.sign_in(config.user, config.password)
db.use(config.namespace, config.database)
_log_query("Database connection established")
yield db
finally:
_log_query("Database connection closed")
db.close()

@classmethod
Expand Down
51 changes: 26 additions & 25 deletions tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,44 +210,45 @@ def test_surrantic_config_singleton():
assert config1 is config2

def test_surrantic_config_default_values():
from surrantic.base import SurranticConfig, SURREAL_ADDRESS, SURREAL_USER, SURREAL_PASS, SURREAL_NAMESPACE, SURREAL_DATABASE
from surrantic.base import SurranticConfig

# Reset to default values
SurranticConfig.reset()
config = SurranticConfig.get_instance()

assert config.address == SURREAL_ADDRESS
assert config.user == SURREAL_USER
assert config.password == SURREAL_PASS
assert config.namespace == SURREAL_NAMESPACE
assert config.database == SURREAL_DATABASE
# Should raise ValueError if we try to get instance without configuration
with pytest.raises(ValueError, match="Missing required configuration"):
SurranticConfig.get_instance()

def test_surrantic_config_override():
from surrantic.base import SurranticConfig

# Store original values
config = SurranticConfig.get_instance()
original_address = config.address
original_user = config.user
# Reset to default values
SurranticConfig.reset()

# Configure with test values
test_address = "ws://localhost:8000"
test_user = "test_user"
test_pass = "test_pass"
test_ns = "test_ns"
test_db = "test_db"

# Override some values
SurranticConfig.configure(
address="ws://testdb:8000",
user="testuser"
address=test_address,
user=test_user,
password=test_pass,
namespace=test_ns,
database=test_db,
debug=True
)

# Check overridden values
assert config.address == "ws://testdb:8000"
assert config.user == "testuser"

# Check non-overridden values remain the same
assert config.password == original_user
config = SurranticConfig.get_instance()

# Reset for other tests
SurranticConfig.configure(
address=original_address,
user=original_user
)
assert config.address == test_address
assert config.user == test_user
assert config.password == test_pass
assert config.namespace == test_ns
assert config.database == test_db
assert config.debug is True

@pytest.mark.asyncio
async def test_db_connection_uses_config(mock_async_db: AsyncMock):
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a4d584c

Please sign in to comment.