Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions scaleway-async/scaleway_async/mongodb/v1/custom_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

from datetime import datetime, timezone
from typing import Optional, Any

from .api import MongodbV1API


def _ensure_tzaware_utc(value: Optional[datetime]) -> Optional[datetime]:
if value is None:
return None
if value.tzinfo is None or value.tzinfo.utcoffset(value) is None:
return value.replace(tzinfo=timezone.utc)
return value


class MongodbUtilsV1API(MongodbV1API):
"""
Async extensions for MongoDB V1.
- Naive datetimes for expires_at are assumed to be UTC.
"""

async def create_snapshot(self, **kwargs: Any) -> Any:
expires_at = kwargs.get("expires_at")
kwargs["expires_at"] = _ensure_tzaware_utc(expires_at)
return await super().create_snapshot(**kwargs)

async def update_snapshot(self, **kwargs: Any) -> Any:
expires_at = kwargs.get("expires_at")
kwargs["expires_at"] = _ensure_tzaware_utc(expires_at)
return await super().update_snapshot(**kwargs)
31 changes: 31 additions & 0 deletions scaleway-async/scaleway_async/mongodb/v1alpha1/custom_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

from datetime import datetime, timezone
from typing import Optional, Any

from .api import MongodbV1Alpha1API


def _ensure_tzaware_utc(value: Optional[datetime]) -> Optional[datetime]:
if value is None:
return None
if value.tzinfo is None or value.tzinfo.utcoffset(value) is None:
return value.replace(tzinfo=timezone.utc)
return value


class MongodbUtilsV1Alpha1API(MongodbV1Alpha1API):
"""
Async extensions for MongoDB V1alpha1.
- Naive datetimes for expires_at are assumed to be UTC.
"""

async def create_snapshot(self, **kwargs: Any) -> Any:
expires_at = kwargs.get("expires_at")
kwargs["expires_at"] = _ensure_tzaware_utc(expires_at)
return await super().create_snapshot(**kwargs)

async def update_snapshot(self, **kwargs: Any) -> Any:
expires_at = kwargs.get("expires_at")
kwargs["expires_at"] = _ensure_tzaware_utc(expires_at)
return await super().update_snapshot(**kwargs)
32 changes: 32 additions & 0 deletions scaleway/scaleway/mongodb/v1/custom_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Optional

from scaleway.mongodb.v1.api import MongodbV1API # type: ignore[import-untyped]


def _ensure_tzaware_utc(value: Optional[datetime]) -> Optional[datetime]:
if value is None:
return None
if value.tzinfo is None or value.tzinfo.utcoffset(value) is None:
return value.replace(tzinfo=timezone.utc)
return value


class MongodbUtilsV1API(MongodbV1API): # type: ignore[misc]
"""
Extensions for MongoDB V1 that provide safer ergonomics.

- Naive datetimes for expires_at are assumed to be UTC.
"""

def create_snapshot(self, **kwargs: Any) -> Any:
expires_at = kwargs.get("expires_at")
kwargs["expires_at"] = _ensure_tzaware_utc(expires_at)
return super().create_snapshot(**kwargs)

def update_snapshot(self, **kwargs: Any) -> Any:
expires_at = kwargs.get("expires_at")
kwargs["expires_at"] = _ensure_tzaware_utc(expires_at)
return super().update_snapshot(**kwargs)
21 changes: 21 additions & 0 deletions scaleway/scaleway/mongodb/v1/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# MongoDB tests (VCR)

This suite uses VCR cassettes to replay HTTP calls in CI.

How to record locally:

1. Ensure you have valid Scaleway credentials exported (access key, secret key, default region).
2. Pick a MongoDB instance to target for snapshot creation.
3. Temporarily replace the fixed `instance_id` in `test_custom_api.py` with your instance id or set breakpoints to inject it.
4. Run the specific test once to record the cassette:

```bash
pytest -k test_create_snapshot_with_naive_expires_at_vcr
```

5. Commit the generated cassette file:
- Path: `scaleway/scaleway/mongodb/v1/tests/cassettes/test_create_snapshot_with_naive_expires_at_vcr.cassette.yaml`

Notes:
- The test will skip in CI if the cassette file is missing.
- After recording, restore the fixed `instance_id` value used by the test to keep requests stable across replays.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
interactions:
- request:
body: '{"instance_id": "dd5cd838-525b-4395-b2ae-4dec3381ceaf", "name": "sdk-python-test-snapshot",
"expires_at": "2025-11-04T11:16:49.007353+00:00"}'
headers:
Content-Length:
- '141'
user-agent:
- scaleway-sdk-python/2.0.0
method: POST
uri: https://api.scaleway.com/mongodb/v1/regions/fr-par/snapshots
response:
body:
string: '{"id": "7d68eb22-4529-452b-af73-160cc621427f", "instance_id": "dd5cd838-525b-4395-b2ae-4dec3381ceaf",
"instance_name": "mgdb-magical-curie", "name": "sdk-python-test-snapshot",
"status": "creating", "created_at": "2025-11-03T10:16:49.164635Z", "updated_at":
"2025-11-03T10:16:49.164635Z", "expires_at": "2025-11-04T11:16:49.007353Z",
"size_bytes": 0, "node_type": "mgdb-play2-nano", "volume_type": "sbs_5k",
"region": "fr-par"}'
headers:
content-length:
- '415'
date:
- Mon, 03 Nov 2025 10:16:49 GMT
server:
- Scaleway API Gateway (fr-par-3;edge01)
x-request-id:
- 68289a8d-c836-47bb-8e92-9ff15be8b012
status:
code: 200
message: OK
- request:
body: '{"instance_id": "dd5cd838-525b-4395-b2ae-4dec3381ceaf", "name": "sdk-python-test-snapshot",
"expires_at": "2025-11-04T11:19:08.742558+00:00"}'
headers:
Content-Length:
- '141'
user-agent:
- scaleway-sdk-python/2.0.0
method: POST
uri: https://api.scaleway.com/mongodb/v1/regions/fr-par/snapshots
response:
body:
string: '{"id": "e604d001-e413-4c55-a27c-909a852a8343", "instance_id": "dd5cd838-525b-4395-b2ae-4dec3381ceaf",
"instance_name": "mgdb-magical-curie", "name": "sdk-python-test-snapshot",
"status": "creating", "created_at": "2025-11-03T10:19:08.876365Z", "updated_at":
"2025-11-03T10:19:08.876365Z", "expires_at": "2025-11-04T11:19:08.742558Z",
"size_bytes": 0, "node_type": "mgdb-play2-nano", "volume_type": "sbs_5k",
"region": "fr-par"}'
headers:
content-length:
- '415'
date:
- Mon, 03 Nov 2025 10:19:09 GMT
server:
- Scaleway API Gateway (fr-par-3;edge01)
x-request-id:
- 408c3493-f594-4fc6-a6eb-f00782ea820a
status:
code: 200
message: OK
version: 1
46 changes: 46 additions & 0 deletions scaleway/scaleway/mongodb/v1/tests/test_custom_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path

import pytest

from vcr_config import scw_vcr
from vcr_config import PYTHON_UPDATE_CASSETTE
from tests.utils import initialize_client_test
from scaleway.mongodb.v1.custom_api import MongodbUtilsV1API


# mypy: ignore-errors


@scw_vcr.use_cassette
def test_create_snapshot_with_naive_expires_at_vcr() -> None:
cassette = (
Path(__file__).with_name("cassettes")
/ "test_create_snapshot_with_naive_expires_at_vcr.cassette.yaml"
)
if not cassette.exists() and not os.getenv("PYTHON_UPDATE_CASSETTE"):
pytest.skip(
"cassette not recorded yet; set PYTHON_UPDATE_CASSETTE=true to record"
)
client = initialize_client_test()
api = MongodbUtilsV1API(client, bypass_validation=True)

# During recording, require a real instance_id via env; during replay, use the fixed value matching the cassette
if PYTHON_UPDATE_CASSETTE:
instance_id = os.getenv("SCW_TEST_MONGODB_INSTANCE_ID")
if not instance_id:
pytest.skip("SCW_TEST_MONGODB_INSTANCE_ID not set while recording")
else:
instance_id = "00000000-0000-0000-0000-000000000000"

# Naive datetime should be handled as UTC by the utils API
naive_dt = datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(days=1)

snapshot = api.create_snapshot(
instance_id=instance_id,
name="sdk-python-test-snapshot",
expires_at=naive_dt,
)

assert snapshot is not None
32 changes: 32 additions & 0 deletions scaleway/scaleway/mongodb/v1alpha1/custom_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Optional

from scaleway.mongodb.v1alpha1.api import MongodbV1Alpha1API # type: ignore[import-untyped]


def _ensure_tzaware_utc(value: Optional[datetime]) -> Optional[datetime]:
if value is None:
return None
if value.tzinfo is None or value.tzinfo.utcoffset(value) is None:
return value.replace(tzinfo=timezone.utc)
return value


class MongodbUtilsV1Alpha1API(MongodbV1Alpha1API): # type: ignore[misc]
"""
Extensions for MongoDB V1alpha1 that provide safer ergonomics.

- Naive datetimes for expires_at are assumed to be UTC.
"""

def create_snapshot(self, **kwargs: Any) -> Any:
expires_at = kwargs.get("expires_at")
kwargs["expires_at"] = _ensure_tzaware_utc(expires_at)
return super().create_snapshot(**kwargs)

def update_snapshot(self, **kwargs: Any) -> Any:
expires_at = kwargs.get("expires_at")
kwargs["expires_at"] = _ensure_tzaware_utc(expires_at)
return super().update_snapshot(**kwargs)