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
9 changes: 8 additions & 1 deletion sdk/src/spectrumx/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import TYPE_CHECKING
from typing import Any
from typing import NoReturn
from typing import Protocol
from typing import TypeVar
from typing import cast

Expand All @@ -36,6 +37,12 @@
logger = logging.getLogger(__name__)


class SupportsProgressUpdate(Protocol):
def update(self, n: int = 1) -> bool | None:
"""Advance progress by ``n`` units."""
...


def validate_file_permission_string(permissions: str) -> None:
"""Make sure a unix-like permissions string is valid."""
perm_flags_len = 9
Expand Down Expand Up @@ -186,7 +193,7 @@ def credit_unstreamed_file_bytes(
*,
file_size: int,
bytes_streamed: int,
prog_bar: tqdm[NoReturn] | None,
prog_bar: SupportsProgressUpdate | None,
bytes_accounted: list[int] | None = None,
) -> int:
"""Credit progress for file bytes that were not transferred over the wire.
Expand Down
2 changes: 2 additions & 0 deletions sdk/tests/models/test_datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,11 @@ def test_parses_raw_gateway_payload_with_nested_coercion(self) -> None:
assert dataset.uuid == ds_uid
assert isinstance(dataset.owner, User)
assert dataset.owner.name == "Owner"
assert dataset.captures is not None
assert isinstance(dataset.captures[0], DatasetCapture)
assert dataset.captures[0].capture_type is CaptureType.DigitalRF
assert isinstance(dataset.captures[0].owner, User)
assert dataset.files is not None
assert isinstance(dataset.files[0], DatasetFile)

def test_model_dump_round_trips(self) -> None:
Expand Down
14 changes: 8 additions & 6 deletions sdk/tests/models/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_create_with_name_and_email(self) -> None:

def test_create_with_defaults(self) -> None:
"""User created with no args has all fields as None."""
user = User()
user = User.model_validate({})
assert user.name is None
assert user.email is None
assert user.uuid is None
Expand Down Expand Up @@ -114,11 +114,13 @@ def test_valid_construction(self) -> None:

def test_item_type_coerced_from_string(self) -> None:
"""item_type accepts string serialization value and coerces to enum."""
perm = UserSharePermission(
item_type="capture",
item_uuid=uuid.uuid4(),
shared_with=self._shared_with(),
permission_level="co-owner",
perm = UserSharePermission.model_validate(
{
"item_type": "capture",
"item_uuid": uuid.uuid4(),
"shared_with": self._shared_with(),
"permission_level": "co-owner",
}
)
assert perm.item_type is ItemType.CAPTURE
assert perm.permission_level is PermissionLevel.CO_OWNER
Expand Down
3 changes: 2 additions & 1 deletion sdk/tests/ops/test_captures.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

if TYPE_CHECKING:
from collections.abc import Generator
from collections.abc import Mapping

import responses
from pydantic import UUID4
Expand Down Expand Up @@ -1732,7 +1733,7 @@ def test_extract_page_from_payload_dict_with_empty_next() -> None:
class _GatewayStub:
"""Minimal stub that emulates the gateway list endpoint."""

def __init__(self, payload: dict[str, object]) -> None:
def __init__(self, payload: Mapping[str, object]) -> None:
self._payload = payload
self.calls = 0

Expand Down
2 changes: 2 additions & 0 deletions sdk/tests/ops/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,7 @@ def test_download_file_to_directory_path(
client=client, file_uuid=file_id.hex, to_local_path=target_dir
)
downloaded_path = result.local_path
assert downloaded_path is not None
assert downloaded_path == expected_path
assert downloaded_path.exists()
assert downloaded_path.stat().st_size == num_bytes
Expand Down Expand Up @@ -1767,6 +1768,7 @@ def test_upload_file_instance_without_directory(client: Client, tmp_path: Path)
name=file_path.name,
media_type="text/plain",
size=file_path.stat().st_size,
directory=PurePosixPath(),
permissions="rw-r--r--",
created_at=datetime(2024, 12, 1, 12, 0, 0, tzinfo=UTC),
updated_at=datetime(2024, 12, 1, 12, 0, 0, tzinfo=UTC),
Expand Down
13 changes: 9 additions & 4 deletions sdk/tests/test_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import uuid
from datetime import datetime
from pathlib import Path
from pathlib import PurePosixPath
from typing import Any
from unittest.mock import patch
from urllib.parse import parse_qs
Expand Down Expand Up @@ -337,7 +338,7 @@ def test_update_existing_file_metadata_sends_put() -> None:
name="test.txt",
media_type="text/plain",
size=100,
directory=Path("/some/dir"),
directory=PurePosixPath("/some/dir"),
permissions="rw-r--r--",
created_at=datetime(2024, 1, 1, tzinfo=UTC),
updated_at=datetime(2024, 1, 1, tzinfo=UTC),
Expand All @@ -361,7 +362,7 @@ def test_update_existing_file_metadata_requires_uuid() -> None:
name="test.txt",
media_type="text/plain",
size=100,
directory=Path("/some/dir"),
directory=PurePosixPath("/some/dir"),
permissions="rw-r--r--",
created_at=datetime(2024, 1, 1, tzinfo=UTC),
updated_at=datetime(2024, 1, 1, tzinfo=UTC),
Expand Down Expand Up @@ -408,7 +409,7 @@ def test_upload_new_file_raises_file_error_when_no_local_path() -> None:
name="test.txt",
media_type="text/plain",
size=100,
directory=Path("/some/dir"),
directory=PurePosixPath("/some/dir"),
permissions="rw-r--r--",
created_at=datetime(2024, 1, 1, tzinfo=UTC),
updated_at=datetime(2024, 1, 1, tzinfo=UTC),
Expand Down Expand Up @@ -446,7 +447,11 @@ def test_create_capture_with_channel_scan_group_name() -> None:
# create_capture sends form-encoded data (requests `data=`), so decode the body
# and assert structured fields — robust to key naming (avoids "groupA" matching
# a "scan_group_name" key by accident, which a substring check would do).
body = parse_qs(responses.calls[0].request.body)
request_body = responses.calls[0].request.body
assert isinstance(request_body, str | bytes)
if isinstance(request_body, bytes):
request_body = request_body.decode()
body = parse_qs(request_body)
assert body["channel"] == ["chan1"]
assert body["scan_group"] == ["groupA"]
assert body["name"] == ["my_capture"]
Expand Down
Loading