Skip to content

Commit

Permalink
feat: add runtime configuration override capability
Browse files Browse the repository at this point in the history
- Add SurranticConfig class for runtime configuration
- Add tests for configuration override
- Update documentation
- Bump version to 0.1.1
  • Loading branch information
lfnovo committed Dec 24, 2024
1 parent 5fb9b56 commit 6c25e78
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 10 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,37 @@ print(f"Updated at: {user.updated}") # Automatically set

## Configuration

### Database Connection

By default, Surrantic uses environment variables for database configuration:

```bash
SURREAL_ADDRESS=ws://localhost:8000
SURREAL_USER=root
SURREAL_PASS=root
SURREAL_NAMESPACE=test
SURREAL_DATABASE=test
```

You can also override these settings directly in your code using `SurranticConfig`:

```python
from surrantic import SurranticConfig

# Override all or some of the connection settings
SurranticConfig.configure(
address="ws://mydb:8000",
user="myuser",
password="mypass",
namespace="myns",
database="mydb"
)

# Your models will now use the new configuration
user = User(name="John", email="[email protected]")
await user.asave() # Uses the custom configuration
```

### Logging

Surrantic includes configurable logging:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "surrantic"
version = "0.1.0"
version = "0.1.1"
description = "A simple Pydantic ORM implementation for SurrealDB"
readme = "README.md"
authors = [
Expand Down
4 changes: 2 additions & 2 deletions src/surrantic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .base import ObjectModel
from .base import ObjectModel, SurranticConfig

__all__ = ["ObjectModel"]
__all__ = ["ObjectModel", "SurranticConfig"]
66 changes: 59 additions & 7 deletions src/surrantic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,56 @@ def _prepare_data(obj: BaseModel) -> str:
items.append(f"{field_name}: {_prepare_value(value)}")
return "{ " + ", ".join(items) + " }"

class SurranticConfig:
"""Configuration class for Surrantic database connection.
This class allows overriding the default database configuration that would
otherwise be loaded from environment variables.
"""
_instance = None

def __init__(self):
self.address = SURREAL_ADDRESS
self.user = SURREAL_USER
self.password = SURREAL_PASS
self.namespace = SURREAL_NAMESPACE
self.database = SURREAL_DATABASE

@classmethod
def get_instance(cls) -> 'SurranticConfig':
"""Get the singleton instance of SurranticConfig"""
if cls._instance is None:
cls._instance = cls()
return cls._instance

@classmethod
def configure(cls,
address: Optional[str] = None,
user: Optional[str] = None,
password: Optional[str] = None,
namespace: Optional[str] = None,
database: Optional[str] = None) -> None:
"""Configure the database connection parameters.
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
"""
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

class ObjectModel(BaseModel):
"""Base model class for SurrealDB objects with CRUD operations.
Expand Down Expand Up @@ -82,11 +132,12 @@ async def _get_db(cls) -> AsyncGenerator[AsyncSurrealDB, None]:
Yields:
AsyncSurrealDB: The configured database connection
"""
db = AsyncSurrealDB(url=SURREAL_ADDRESS)
config = SurranticConfig.get_instance()
db = AsyncSurrealDB(url=config.address)
try:
await db.connect()
await db.sign_in(SURREAL_USER, SURREAL_PASS)
await db.use(SURREAL_NAMESPACE, SURREAL_DATABASE)
await db.sign_in(config.user, config.password)
await db.use(config.namespace, config.database)
logger.debug("Database connection established")
yield db
finally:
Expand All @@ -99,13 +150,14 @@ def _get_sync_db(cls) -> Generator[SurrealDB, None, None]:
"""Get a configured synchronous database connection as a context manager.
Yields:
SurrealDB: The configured synchronous database connection
SurrealDB: The configured database connection
"""
db = SurrealDB(SURREAL_ADDRESS)
config = SurranticConfig.get_instance()
db = SurrealDB(url=config.address)
try:
db.connect()
db.sign_in(SURREAL_USER, SURREAL_PASS)
db.use(SURREAL_NAMESPACE, SURREAL_DATABASE)
db.sign_in(config.user, config.password)
db.use(config.namespace, config.database)
logger.debug("Database connection established")
yield db
finally:
Expand Down
67 changes: 67 additions & 0 deletions tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,70 @@ async def test_aget_all_with_ordering(mock_async_db: AsyncMock) -> None:
await TestModel.aget_all(order_by="name", order_direction="DESC")

mock_async_db.query.assert_called_once_with("SELECT * FROM test_table ORDER BY name DESC")

def test_surrantic_config_singleton():
from surrantic.base import SurranticConfig

config1 = SurranticConfig.get_instance()
config2 = SurranticConfig.get_instance()

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

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

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

# Override some values
SurranticConfig.configure(
address="ws://testdb:8000",
user="testuser"
)

# 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

# Reset for other tests
SurranticConfig.configure(
address=original_address,
user=original_user
)

@pytest.mark.asyncio
async def test_db_connection_uses_config(mock_async_db: AsyncMock):
from surrantic.base import SurranticConfig

# Configure custom connection details
SurranticConfig.configure(
address="ws://testdb:8000",
user="testuser",
password="testpass",
namespace="testns",
database="testdb"
)

model = TestModel(name="Test", age=25)
await model.asave()

# Verify the connection was made with our custom config
mock_async_db.connect.assert_called_once()
mock_async_db.sign_in.assert_called_once_with("testuser", "testpass")
mock_async_db.use.assert_called_once_with("testns", "testdb")

0 comments on commit 6c25e78

Please sign in to comment.